import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class MainWindow extends JFrame implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, FocusListener, WindowListener
{
    // Conventions:
    // -we initialize class members even with Java's default initialization value to show
    // that the value is crucial for the program execution (means null, 0, 0.0 etc.)
    // -methods have a capital letter at the beginning, although this is against Java's
    // naming convention, to mark them as NCIDE-intern
    // -nouns in comments are capitalized if they refer to a code variable or method
    // -in if ()-statement: braces behind ! to not overlook it ("if (!(o.IsNeuron()))")

    // ************************************************************************

    private class DrawPane extends JPanel
    {
        private static final long serialVersionUID = 1L;

        private Grid Grid = new Grid();

        private long DrawTime[] = new long[4]; // average draw times, if average exceeds DrawTimeMinForPatienceMessage, wait cursor displayed
        private long DrawTimeMinForPatienceMessage = 100; // wait cursor comes often with 100, but then program works like chewing gum anyway - 100 is ok

        private ReentrantLock paintComponentLock = new ReentrantLock();

        private void DrawMultiLineString(Graphics g, String text, int x, int y)
        { // https://programming.guide/java/drawing-multiline-strings-with-graphics.html

            int lineHeight = g.getFontMetrics().getHeight();
            for (String line : text.split("\n"))
                g.drawString(line, x, y += lineHeight); // TODO: ZoomFactor (?!?)
        }

        @Override
        public void paintComponent(Graphics g)
        {
            // NOTE: I'm not sure if the following lock is effective, maybe it is impossible to
            // call the paintComponent() method concurrently (but, what if window resized +
            // simulation running at the same time?) - however, in test the lock caused no
            // problems and I did not observe any negative effects, instead I had the impression
            // that the system works a bit smoother with it:
            // ->
            if (paintComponentLock.tryLock() == false)
                return;

            // System.out.println("DoRedraw " + System.currentTimeMillis());

            long DrawTimeStart = System.currentTimeMillis();

            UpdateMainWindowSizeInfo(); // MUST be done here to avoid problems when opening MainWindow in maximized state, this.getWidth()/getHeight() returned preferred size although maximized, all attempts to fix this in main() failed

            long DrawTimeAverage = 0;
            for (int m = DrawTime.length - 1; m >= 1; m--)
                DrawTimeAverage += DrawTime[m];
            DrawTimeAverage = (long) (DrawTimeAverage / DrawTime.length);

            if (DrawTimeAverage >= DrawTimeMinForPatienceMessage)
            {
                CursorManager.SetWaitCursorEnabled(true);
                // works, but annoying:
                // GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText("DRAWING", false, false, true, false); // will show up only if processing input takes a longer time (e.g. drawing millions of objects)
            }

            if (!(BackUpRestorePending))
            {
                // draw grid
                Grid.Draw(g);

                // draw all objects over grid
                EventManager.GetObjectStorage().Draw(g, IsHandsomeDrawOrderEnabled());

                // draw Ticker's text
                GUIElements.GetTicker().Draw(g);

                // draw HelpText (if any)
                if (HelpText != null)
                {
                    if (!(HelpText.startsWith("[NO_BACKGROUND]")))
                    {
                        // HELP TEXT WITH BACKGROUND

                        g.setColor(Color.DARK_GRAY); // BLACK looks uglier
                        g.fillRect(50, 50, 1450, 950);
                        g.setFont(new Font("Monospaced", Font.PLAIN, 18));
                        g.setColor(Color.WHITE);
                        DrawMultiLineString(g, HelpText, 55, this.getInsets().top + 50);
                    }
                    else
                    {
                        // HELP TEXT, TRANSPARENT BACKGROUND

                        g.setFont(new Font("Monospaced", Font.PLAIN, 18));
                        g.setColor(Color.BLACK);
                        DrawMultiLineString(g, HelpText.substring("[NO_BACKGROUND]".length()), 55, this.getInsets().top + 0); // no top margin
                    }
                }
            }
            else
            {
                // BACKUP RESTORE

                g.setColor(Color.DARK_GRAY);
                g.fillRect(0, 0, MainWindowSizeX, MainWindowSizeY);

                // draw grid
                // Grid.Draw(g);

                g.setFont(new Font("Monospaced", Font.PLAIN, 21)); // don't touch this :)
                g.setColor(Color.WHITE);
                DrawMultiLineString(g, BackUpRestoreText, 55, this.getInsets().top + 10);
            }

            if (DrawTimeAverage >= DrawTimeMinForPatienceMessage)
            {
                // works, but annoying:
                // GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoText("DRAWING");
                CursorManager.SetWaitCursorEnabled(false);
            }

            long DrawTimeEnd = System.currentTimeMillis();

            for (int m = DrawTime.length - 1; m >= 1; m--)
                DrawTime[m] = DrawTime[m - 1];
            DrawTime[0] = DrawTimeEnd - DrawTimeStart;

            // System.out.println("End of DoRedraw");

            paintComponentLock.unlock();
            return;
        }
    }

    // ************************************************************************

    public class GUIElements
    {
        private DrawPane DrawPane;
        private IOPanel IOPanel;
        private MainWindow MainWindow;
        private Ticker Ticker;

        public DrawPane GetDrawPane()
        {
            return DrawPane;
        }

        public IOPanel GetIOPanel()
        {
            return IOPanel;
        }

        public MainWindow GetMainWindow()
        {
            return MainWindow;
        }

        public Ticker GetTicker()
        {
            return Ticker;
        }
    }

    private GUIElements GUIElements = new GUIElements();

    private MouseEventProcessor MouseEventProcessor = new MouseEventProcessor(this);
    private KeyEventProcessor KeyEventProcessor = new KeyEventProcessor(this);
    private EventManager EventManager = new EventManager();

    private final String ProgramVersion = "[v1.0 Beta 5]";

    private boolean BackUpRestorePending = false;
    private String BackUpRestoreText = "";
    private ArrayList<String> BackUpRestoreFiles = new ArrayList<String>();

    private boolean HandsomeDrawOrderEnabled = false;

    private int HelpTextIndex = -1;
    private static String HelpText = null;

    private Timer MemoryCheckerTimer;
    private TimerTask MemoryCheckerTimerTask;

    private boolean RedrawEnabled = true;
    private boolean RedrawRequested = false;

    private TitleBarInfo TitleBarInfo = null;

    // ************************************************************************

    private static final long serialVersionUID = 1L; // else Eclipse keeps on annoying

    // for DrawObject.IsInWindowBoundaries(), use static variables for speed reasons (no object references to resolve at runtime, I _suppose_):
    public static int MainWindowSizeX;
    public static int MainWindowSizeY;

    public static void main(String... args) throws Exception
    {
        MainWindow frame = new MainWindow("");

        CursorManager.SetWindow(frame);

        frame.setLocationRelativeTo(null);
        frame.setLocation(100, 50); // we assume everyone uses at least full HD resolution
        frame.setPreferredSize(new Dimension(1800, 1000));
        frame.setExtendedState(frame.getExtendedState() | Frame.MAXIMIZED_BOTH); // maximize MainWindow
        frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); // system closes window
        frame.pack();
        frame.GetTitleBarInfo().UpdateTitleBarInfo(); // do once manually to verify text is visible instantly after MainWindow showed up
        frame.setVisible(true);
    }

    // ************************************************************************

    public MainWindow(String FrameTitle)
    {
        this.setTitle(FrameTitle);

        this.addKeyListener(this);
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addMouseWheelListener(this);
        this.addFocusListener(this);
        this.addWindowListener(this);

        GUIElements.MainWindow = this;
        GUIElements.DrawPane = new DrawPane();

        this.setContentPane(GUIElements.DrawPane);

        this.setLayout(new FlowLayout(FlowLayout.LEFT)); // https://www.javatpoint.com/FlowLayout

        GUIElements.IOPanel = new IOPanel(this);

        this.add(GUIElements.IOPanel);

        this.TitleBarInfo = new TitleBarInfo(this, "Neuron Connection IDE " + ProgramVersion + " - F9 for help");

        GUIElements.Ticker = new Ticker();

        InstallMemoryChecker();
    }

    public void DisableBackUpRestore()
    {
        BackUpRestorePending = false;
        BackUpRestoreText = "";
    }

    public void DisableHandsomeDrawOrder()
    {
        HandsomeDrawOrderEnabled = false;
    }

    public void DisableRedraw()
    {
        RedrawEnabled = false;
    }

    public void DoRedraw()
    {
        this.UpdateMainWindowSizeInfo();

        if (RedrawEnabled)
        {
            this.invalidate();
            this.validate();
            this.repaint();

            RedrawRequested = false;
        }
        else
        {
            // "collect" requests for redrawing and finally do
            // redrawing ONCE when EnableRedraw() is called
            // (RedrawRequested is some kind of "dirty flag")

            RedrawRequested = true;
        }
    }

    public void EnableBackUpRestore(String CurrentWorkingDirectory, String CurrentOverrideFileName)
    {
        BackUpRestorePending = true;

        UpdateBackUpRestoreText(CurrentWorkingDirectory, CurrentOverrideFileName);
    }

    public void EnableHandsomeDrawOrder()
    {
        HandsomeDrawOrderEnabled = true;
    }

    public void EnableRedraw()
    {
        RedrawEnabled = true;

        if (RedrawRequested)
        {
            DoRedraw();

            RedrawRequested = false;
        }
    }

    @Override
    public void focusGained(FocusEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void focusLost(FocusEvent arg0)
    {
        // we HAVE to do this manually, in tests keyReleased() was not called
        // when file open dialog opened 'out of' keyPressed():

        // System.out.println("focus lost");

        MouseEventProcessor.ClearMouseButtonClicked();
        MouseEventProcessor.ClearMouseButtonDown();

        KeyEventProcessor.ClearKeyCodePressed();
        KeyEventProcessor.ClearKeyTyped();
    }

    public ArrayList<String> GetBackUpRestoreFiles()
    {
        return BackUpRestoreFiles;
    }

    public GUIElements GetGUIElements()
    {
        return GUIElements;
    }

    public TitleBarInfo GetTitleBarInfo()
    {
        return TitleBarInfo;
    }

    private void InstallMemoryChecker()
    {
        MemoryCheckerTimerTask = new TimerTask()
        {
            @Override
            public void run()
            {
                if (Tools.CheckHeapSize() == false)
                    GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText("!!! OUT OF MEMORY !!!", true, false, false, false);
                // message blinks if added every 5 seconds and removed by TitleBarInfo every 3 seconds!
            }
        };

        MemoryCheckerTimer = new Timer();
        MemoryCheckerTimer.schedule(MemoryCheckerTimerTask, 0, 5000);
    }

    public boolean IsBackUpRestoreEnabled()
    {
        return BackUpRestorePending;
    }

    public boolean IsHandsomeDrawOrderEnabled()
    {
        return HandsomeDrawOrderEnabled;
    }

    public boolean IsHelpEnabled()
    {
        return (HelpText != null);
    }

    @Override
    public void keyPressed(KeyEvent arg0)
    {
        // TODO Auto-generated method stub

        // System.out.println("key pressed");

        KeyEventProcessor.keyPressed(arg0);

        EventManager.TryProcessUserKeyboardAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    @Override
    public void keyReleased(KeyEvent arg0)
    {
        // TODO Auto-generated method stub

        // System.out.println("key released");

        KeyEventProcessor.keyReleased(arg0);

        EventManager.TryProcessUserKeyboardAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    @Override
    public void keyTyped(KeyEvent arg0)
    {
        // TODO Auto-generated method stub

        // System.out.println("key typed");

        KeyEventProcessor.keyTyped(arg0);

        EventManager.TryProcessUserKeyboardAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        KeyEventProcessor.ClearKeyTyped();
    }

    private String LINE(String S)
    {
        return (S + "\r\n");
    }

    @Override
    public void mouseClicked(MouseEvent arg0)
    {
        // System.out.println("clicked");

        MouseEventProcessor.mouseClicked(arg0);

        EventManager.TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        MouseEventProcessor.ClearMouseButtonClicked();
    }

    @Override
    public void mouseDragged(MouseEvent arg0)
    {
        // System.out.println("dragged");

        MouseEventProcessor.mouseDragged(arg0);

        EventManager.TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    @Override
    public void mouseEntered(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseExited(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseMoved(MouseEvent arg0)
    {
        // System.out.println("moved");

        MouseEventProcessor.mouseMoved(arg0);

        EventManager.TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    @Override
    public void mousePressed(MouseEvent arg0)
    {
        // System.out.println("pressed " + arg0.getButton());

        MouseEventProcessor.mousePressed(arg0);

        EventManager.TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    @Override
    public void mouseReleased(MouseEvent arg0)
    {
        // System.out.println("released");

        MouseEventProcessor.mouseReleased(arg0);

        EventManager.TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e)
    {
        // TODO Auto-generated method stub

        MouseEventProcessor.mouseWheelMoved(e);

        EventManager.TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        MouseEventProcessor.ResetMouseWheel();
    }

    private void ShutDown()
    {
        if (EventManager.GetIOProgram().IsExecuted())
        {
            System.out.println("stopping execution of simulation...");

            EventManager.GetIOProgram().StopExecution();
            for (int Wait = 0; Wait < 10 * (1000 / 100); Wait++)
            {
                if (!(EventManager.GetIOProgram().IsExecuted()))
                    break;
                try
                {
                    Thread.sleep(100); // the current thread, see https://www.digitalocean.com/community/tutorials/thread-sleep-java
                }
                catch (InterruptedException e)
                {
                    // ignore exception
                }
            }

            System.out.println("simulation still executed? " + EventManager.GetIOProgram().IsExecuted());
        }
        this.dispose();
        System.out.println("now calling System.exit(0)...");
        System.exit(0); // tutorials on the Internet all recommend this way to shut down
    }

    @SuppressWarnings("static-access")
    public void ToggleHelpText()
    {
        if (HelpTextIndex < 0)
        {
            HelpTextIndex = 0;
            HelpText = LINE("HELP, PAGE 1 - Neuron Connection IDE")
                + LINE("")
                + LINE("Kind of scientific tool to determine probability of certain neuron interconnections")
                + LINE("(what does work is more likely to appear in human/mammal/bird brain than non-working stuff ;) )")
                + LINE("")
                + LINE("Not intended for practical use by John Doe")
                + LINE("")
                + LINE("Create an object by holding left mouse button and pressing a specific key:")
                + LINE("")
                + LINE("t - TextField")
                + LINE("f - Frame [actually a generated set of a TextField and a set of Nodes and Lines, special colors]")
                + LINE("n - Neuron [user sets input weights - if sum is 1.0, output node fires (all linear calculations)]")
                + LINE("l - LearningNeuron [input weights increase if they are excited concurrently with one \"n\" input]")
                + LINE("c - Node [\"connection intermediate point\"]")
                + LINE("v - VisualField")
                + LINE("")
                + LINE("Left-click to connect objects with Lines (just try out - ATTENTION: direction matters)")
                + LINE("Right-click to alter some objects (just try out)")
                + LINE("")
                + LINE("Ctrl + left-click             : select objects one by one")
                + LINE("Shift + left-press + dragging : selection window")
                + LINE("")
                + LINE("Left-click on object          : select, press + drag to move")
                + LINE("Right-press on grid + dragging: shift all objects (scroll)")
                + LINE("")
                + LINE("h on Neuron   : assign Hormone (enter any name, put in front \"del ...\" or \"ren ...\" to delete/rename)")
                + LINE("h on grid     : set Hormone strength during simulation")
                + LINE("n on Neuron   : negate single input [normal Neuron] or mark input as enabling \"learning\" (see above)")
                + LINE("1-9           : toggle input weight 1-9 of all Neurons selected (not more than 9 inputs changeable here)")
                + LINE("")
                + LINE("Del-key       : delete selected or pointed at object(s)")
                + LINE("")
                + LINE("F1, F2, F3, F4: load new file, load contents and insert, save as new file, override file saved or loaded last")
                + LINE("F5, F6        : toggle min/max zoom, restore user's zoom after F5")
                + LINE("F7, F8        : copy selection, paste selection (does also work across multiple program instances)")
                + LINE("F9            : toggle this help")
                + LINE("F10           : toggle IO panel providing IO program")
                + LINE("F11           : start simulation, point on object or create a selection and press space bar to excite")
                + LINE("F12           : additionally work up IO program");
        }
        else if (HelpTextIndex < 1)
        {
            HelpTextIndex = 1;
            HelpText = LINE("HELP, PAGE 2 - Details")
                + LINE("")
                + LINE("IO program: write text of any TextField into text box of IO panel, press F12 to work up IO program line by Line.")
                + LINE("Those TextFields are excited that contain the text appearing in IO program.")
                + LINE("Activate multiple TextFields in one line by separating their text with / (e.g. \"John/in his house/sun is shining\").")
                + LINE("Write \"REPEAT\" in a single line for endless looping until next F12 press.")
                + LINE("Goto-like commands supported: \"GOTO=[e.g. ]free will test\" jumps to line containing only (!) \"free will test\".")
                + LINE("IO program is saved/reloaded together with objects (F1-F4).")
                + LINE("Text box of IO panel also outputs text of TextFields being excited during simulation.")
                + LINE("Do not use [ and ] chars in your IO programs, they bound system output.")
                + LINE("System output is automatically removed when starting a new simulation.")
                + LINE("")
                + LINE("Each (yellow) flash, which is an excitement, lasts one tick (except VisualFields, they have an \"afterglow\",")
                + LINE("for better visibility). Each excitement emitted, and such conducted by Lines, has the same strength (1.0).")
                + LINE("The system attempts to do " + Simulation.GetTicksPerSecond() + " ticks per second (limited by single-thread CPU power).")
                + LINE("Each " + Simulation.TicksPerIOTextAreaLine() + " ticks a new line from IO panel's text box is worked up.")
                + LINE("This means the system works up around " + (int) ((double) Simulation.GetTicksPerSecond() / (double) Simulation.TicksPerIOTextAreaLine()) + " lines per second.")
                + LINE("")
                + LINE("Neurons emit excitement if the sum of their inputs reaches or exceeds 1.0. Each bar you see in a Neuron has")
                + LINE("a stepping, if no bar is visible, the input excitement (1.0) is multiplied by 0.0, if the full bar is visible,")
                + LINE("by 1.0. Notice this program uses exclusively linear calculations, half-visible bars represent exactly half of")
                + LINE("the maximal factor, and excitement decreases linearly.")
                + LINE("")
                + LINE("Hormones have a name and, during simulation, an adjustable strength in the range -100 to 100 (percent).")
                + LINE("Strength is eventually a factor (0.0 to 2.0) for the excitement flow in the related Neuron, e.g., Adrenaline=0")
                + LINE("in IO program keeps Hormone ineffective, Adrenaline=100 makes all Neurons containing this Hormone twice as")
                + LINE("sensitive to incoming flow.")
                + LINE("")
                + LINE("Mind the timing of excitement, traversing a Line element lasts one tick.")
                + LINE("")
                + LINE("Regards, louis@louis-coder.com")
                + LINE("")
                + LINE("STATISTICS:")
                + LINE(EventManager.GetObjectStorage().GetDrawObjectCount() + " objects")
                + LINE("F4 saves as \"" + (EventManager.GetLoadSaveManager().GetOverridePath() != null ? EventManager.GetLoadSaveManager().GetOverridePath() : "[not defined, load (F1) or save (F3) any file first]") + "\"")
                + LINE("WorkingDirectory is \"" + EventManager.GetSettings().GetWorkingDirectory() + "\"") + LINE("    (automatically set to the directory where a file was saved (F3) last)");
        }
        else if (HelpTextIndex < 2)
        {
            HelpTextIndex = 2;
            HelpText = LINE("HELP, PAGE 3 - CADU (1/2)")
                + LINE("")
                + LINE("Control + u generates a \"ChaoticalANDDetectionUnit\" (abbreviated \"CADU\", my own neologism).")
                + LINE("")
                + LINE("Each CADU consists of the following components:")
                + LINE("-at the left a vertical Line pathway,")
                + LINE("-at the right an array of Neurons that do all have fixed input weights,")
                + LINE("-at the bottom a row of LearningNeurons (optional).")
                + LINE("")
                + LINE("The idea is the following:")
                + LINE("-at top of the vertical Line pathway, a specific pattern of excitement is applied,")
                + LINE("-excitement \"travels\" through the vertical Line pathway top down, not instantly, but")
                + LINE(" one Line per Simulation tick,")
                + LINE("-there's at least one Neuron in the right array whose inputs are \"by chance\" connected to such Nodes in the")
                + LINE(" vertical Line pathway so that they all get excitement from the right Line (the x-location in the pathway)")
                + LINE(" at the right time (the y-location in the pathway).")
                + LINE("")
                + LINE("This means, for any thinkable excitement pattern, there's at least one Neuron that reacts to")
                + LINE("this excitement pattern, also over time (because the excitement needs some time to traverse the")
                + LINE("vertical Line pathway).")
                + LINE("")
                + LINE("In contrast to the neural networks commonly used in computer science, no input pattern is \"learned\",")
                + LINE("it is \"already there\", encoded in the mass of connections between neurons (the right array)")
                + LINE("to axons of other neurons (the vertical Line pathway). Merely the right Neuron's output must be selected.")
                + LINE("In my tests, this method recognized input patterns much more reliably than approaches with solely LearningNeurons,")
                + LINE("there was never the notorious \"overfitting\" or later reaction to the \"wrong\" input pattern.")
                + LINE("")
                + LINE("Even CADUs may contain LearningNeurons at the bottom, but they do not learn the (main) input pattern, but")
                + LINE("do the \"selection\" of the right Neuron from the Neuron array: the output of every Neuron is connected with")
                + LINE("one input of each LearningNeuron, when the negative input of some LearningNeuron is excited concurrently with")
                + LINE("the Neuron which \"reacts\" to the input pattern, the LearningNeuron does from then on always \"forward\" this")
                + LINE("excitement to one Node in the right-most Node array at the top of the CADU (demonstrated in some sample")
                + LINE(".ncide files).")
                + LINE("")
                + LINE("-with the size of the input pattern, and the time, the number of required neurons and connections grows")
                + LINE(" exponentially (the common calculations to determine the number of combinations do apply)")
                + LINE("-it might be necessary to do the CADU-like pattern recognition pyramidal (several CADUs' output is input of")
                + LINE(" the next one(s)), to avoid an impossible number of connections (even billions of synapses wouldn't suffice)")
                + LINE("-this applies also to the recognition over time: it would require too many connections to process large/long-")
                + LINE(" lasting patterns \"at once\", their processing MUST be \"split up\" (=> in the real brain: short term memory (?!?))")
                + LINE("-this could be the reason why too long sentences are not understandable for humans, and there was")
                + LINE(" no contrary development in evolution");
        }
        else if (HelpTextIndex < 3)
        {
            HelpTextIndex = 3;
            HelpText = LINE("HELP, PAGE 4 - CADU (2/2)")
                + LINE("")
                + LINE("With a slightly modified usage of the CADU, the \"opposite\" of recognition can be achieved: \"generating\" patterns.")
                + LINE("Please load (F1) the file Test.SentenceToWordsToLetters.ncide (in the Beta 2 Zip) and study the interconnections")
                + LINE("carefully.")
                + LINE("")
                + LINE("If the CADU approach should in fact be implemented (similarly) in the human/mammal/bird brain, this would explain why")
                + LINE("1) milliards of synapses are required, 2) cascading processing in the brain must be commonly done, 3) there are not")
                + LINE("the typical problems known from neural networks used in computer science (overfitting, very high count of training")
                + LINE("iterations required).");
        }
        else if (HelpTextIndex < 4)
        {
            HelpTextIndex = 4;
            HelpText = LINE("[NO_BACKGROUND]HELP, PAGE 5 - Keys")
                + LINE("")
                + LINE("left-click + c - create Node")
                + LINE("left-click + f - create CommentField")
                + LINE("left-click + l - create LearningNeuron [input weights grow if excited concurrently with one negative input]")
                + LINE("left-click + n - create Neuron [input weights set by user]")
                + LINE("left-click + s - create SampleAndHold [keeps excitement durably for a time span set by user]")
                + LINE("left-click + t - create TextField")
                + LINE("left-click + v - create VisualField")
                + LINE("Control + a - create TextField array [texts out of file]")
                + LINE("Control + b - restore backup")
                + LINE("Control + d - duplicate objects in selection")
                + LINE("Control + h - set current strength of Hormone [during simulation only]")
                + LINE("Control + r - toggle reduced drawing mode [save some CPU time]")
                + LINE("n   - set input of a Neuron or LearningNeuron negative")
                + LINE("1-9 - toggle all input weights of Neurons in selection")
                + LINE("n   - increase hold time of one or more (if in selection) SampleAndHold objects by a fixed amount")
                + LINE("n/e - temporarily/durably avoid TextField activation")
                + LINE("")
                + LINE("right-click : increase one input weight of a Neuron")
                + LINE("right-click : increase hold time of one SampleAndHold object")
                + LINE("")
                + LINE("Del         : delete objects in selection")
                + LINE("")
                + LINE("F1          : load file")
                + LINE("F2          : insert content of file")
                + LINE("F3          : save file")
                + LINE("F4          : quick save [override last saved/loaded file]")
                + LINE("F5          : toggle min/max zoom [\"jump out, point to new location and jump in\"]")
                + LINE("F6          : restore zoom used before F5 press")
                + LINE("F7          : copy objects in selection")
                + LINE("Control + F7: set Nodes in selection as start points for Line connections")
                + LINE("F8          : paste objects copied with F7")
                + LINE("Control + F8: set Nodes in selection as end points for Line connections, finally create Lines")
                + LINE("F9          : help")
                + LINE("F10         : view/edit IO program [excites TextFields containing text appearing in IO program]")
                + LINE("F11         : start simulation, manual excitement with space bar only")
                + LINE("F12         : start simulation, work up IO program line by line, additionally manual excitement")
                + LINE("")
                + LINE("left-press + drag          : move object(s)")
                + LINE("right-press + drag         : scroll")
                + LINE("")
                + LINE("mouse wheel                : zoom")
                + LINE("")
                + LINE("Shift + left-press + drag  : multi-select objects")
                + LINE("Control + left-click       : select/deselect single objects");
        }
        else
        {
            HelpTextIndex = -1;
            HelpText = null;
        }
    }

    private void UpdateBackUpRestoreText(String CurrentWorkingDirectory, String CurrentOverrideFileName)
    {
        // https://stackoverflow.com/questions/5694385/getting-the-filenames-of-all-files-in-a-folder

        BackUpRestoreText = "";
        BackUpRestoreFiles.clear();

        if (CurrentOverrideFileName != null && CurrentOverrideFileName.length() >= 1 &&
            CurrentWorkingDirectory != null && CurrentWorkingDirectory.length() >= 1) // fix in Beta 3
        {
            File folder = new File((CurrentWorkingDirectory + "/" + "BackUp/").replace("//", "/"));
            File[] listOfFiles = folder.listFiles();

            if (listOfFiles != null) // is null if BackUp dir is not existing (tested)
            {
                for (int i = 0; i < listOfFiles.length; i++)
                {
                    if (listOfFiles[i].isFile())
                    {
                        System.out.println("File " + listOfFiles[i].getName());

                        if (listOfFiles[i].getName().contains(CurrentOverrideFileName) &&
                            listOfFiles[i].getAbsolutePath().contains(CurrentWorkingDirectory)) // TODO: make this "safer"
                        {
                            try
                            {
                                BackUpRestoreFiles.add(
                                    listOfFiles[i].getAbsolutePath() + " <" +
                                        Files.readAttributes(listOfFiles[i].toPath(), BasicFileAttributes.class).lastModifiedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(
                                            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
                                        ) + ">"
                                );
                            }
                            catch (IOException e)
                            {
                                // ignore error
                            }
                        }
                    }
                    else if (listOfFiles[i].isDirectory())
                    {
                        System.out.println("Directory " + listOfFiles[i].getName());
                    }
                }
            }
        }

        Collections.sort(BackUpRestoreFiles, Collections.reverseOrder());

        BackUpRestoreText = "Backup versions automatically created when saving (F3 or F4), the higher the number, the newer.\r\nClick on file to load its content and replace current session's objects, or click HERE to abort.\r\n\r\n\r\n";

        for (int m = 0; m < BackUpRestoreFiles.size(); m++)
        {
            BackUpRestoreText += BackUpRestoreFiles.get(m) + "\r\n\r\n";
            BackUpRestoreFiles.set(m, BackUpRestoreFiles.get(m).substring(0, BackUpRestoreFiles.get(m).lastIndexOf(" <")));
        }
    }

    public void UpdateHelpText()
    {
        if (HelpTextIndex >= 0)
            HelpTextIndex--; // "undo" help page toggle in advance
        ToggleHelpText();
    }

    public void UpdateMainWindowSizeInfo()
    {
        MainWindowSizeX = this.getWidth();
        MainWindowSizeY = this.getHeight();

        ZoomManager.SetDrawWidthAndHeight(MainWindowSizeX, MainWindowSizeY);
    }

    @Override
    public void windowActivated(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowClosed(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowClosing(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

        boolean ChangesExisting = EventManager.CheckForChangesExisting(GUIElements, KeyEventProcessor, MouseEventProcessor);
        if (ChangesExisting)
        {
            if (Tools.ShowYesNoDialog(this, "Unsaved changes were made. Stay to save?"))
            {
                // just continue program execution, MainWindow.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE) avoids closing MainWindow
            }
            else
            {
                ShutDown();
            }
        }
        else
        {
            ShutDown();
        }
    }

    @Override
    public void windowDeactivated(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowDeiconified(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowIconified(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowOpened(WindowEvent arg0)
    {
        // TODO Auto-generated method stub

    }
}
